/**
 * Copyright (c) 2012-2014 All Rights Reserved.
 */
package com.easy.framwork.jdbc.handlers;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.easy.framwork.exceptions.ConvertException;
import com.easy.framwork.jdbc.JdbcUtils;
import com.easy.framwork.utils.StringUtils;
import com.easy.framwork.utils.bean.PropertyDescriptorsCache;

/**
 * 
 * @author wy
 * @version v 0.1 2014-1-2 下午9:31:14 wy Exp $
 * @param <T>
 */
public class SingleBeanHandler<T> implements ResultSetHandler<T> {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    private Class<T> requiredType;

    /**
     * Create a new SingleBeanHandler.
     * 
     * @param requiredType the type that each result object is expected to match
     */
    public SingleBeanHandler(Class<T> requiredType) {
        this.requiredType = requiredType;
    }

    /**
     * Set the type that each result object is expected to match.
     * <p>
     * If not specified, the column value will be exposed as returned by the
     * JDBC driver.
     */
    public void setRequiredType(Class<T> requiredType) {
        this.requiredType = requiredType;
    }

    /**
     * @see com.easy.framwork.jdbc.handlers.ResultSetHandler#handle(java.sql.ResultSet)
     */
    @SuppressWarnings("unchecked")
    @Override
    public T handle(ResultSet rs) throws SQLException {

        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();
        PropertyDescriptorsCache cache = PropertyDescriptorsCache.forClass(requiredType);

        Object object = null;
        int row = 0;
        while (rs.next()) {
            row++;
            if (Object.class.equals(requiredType) || JdbcUtils.isJdbcType(requiredType)) {
                
                if (cols > 1) {
                    throw new SQLException("ResultSet column count than 1");
                }

                object = JdbcUtils.getResultSetValue(rs, 1,
                    Object.class.equals(requiredType) ? null : requiredType);
            } else {
                object = getValue(rs, rsmd, cols, cache, object);
            }

        }
        if (row > 1) {
            throw new SQLException("ResultSet row count than 1");
        }
        return (T) object;
    }

    /**
     * 
     * @param rs
     * @param rsmd
     * @param cols
     * @param cache
     * @param object
     * @return
     * @throws SQLException
     */
    protected Object getValue(ResultSet rs, ResultSetMetaData rsmd, int cols,
                              PropertyDescriptorsCache cache, Object object) throws SQLException {
        try {
            object = requiredType.newInstance();
        }
        catch (InstantiationException e) {
            log.error("instantiation class {} error,:{}", requiredType, e);
            throw new ConvertException("instantiation class " + requiredType + "error");
        }
        catch (IllegalAccessException e) {
            log.error("illegal access for instance class {} error,:{}", requiredType, e);
            throw new ConvertException("illegal access for instance class " + requiredType);
        }

        for (int i = 1; i <= cols; i++) {
            String columnName = rsmd.getColumnName(i);
            PropertyDescriptor pd = cache.getPropertyDescriptor(columnName);
            if (pd == null) {
                String propertyName = StringUtils.toPropertyName(columnName);
                pd = cache.getPropertyDescriptor(propertyName);
            }
            if (pd == null || pd.getWriteMethod() == null) {
                continue;
            }

            Method method = pd.getWriteMethod();

            Class<?> type = method.getParameterTypes()[0];
            if (!JdbcUtils.isJdbcType(type)) {
                log.warn("{} property {} is not jdbc type", requiredType.getName(), columnName);
                continue;
            }

            Object value = JdbcUtils.getResultSetValue(rs, i, type);

            try {
                if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
                    method.setAccessible(true);
                }
                method.invoke(object, new Object[] { value });
            }
            catch (Exception e) {
                log.error("invoke class {}#method{} error,:{}",
                    new Object[] { requiredType, method.getName(), e });
                throw new ConvertException("instantiation class " + requiredType + "#method:"
                                        + method.getName() + "error");
            }

        }
        return object;
    }

}
